import { compileMessage } from "./compileMessage.js"
import { ProjectSettings, type Message, LanguageTag, lookup } from "@inlang/sdk"
import { telemetry } from "../services/telemetry/implementation.js"
import { i } from "../services/codegen/identifier.js"
import { getStackInfo } from "../services/telemetry/stack-detection.js"
import fs from "node:fs/promises"
import { getPackageJson } from "../services/environment/package.js"
import { createRuntime } from "./runtime.js"

const ignoreDirectory = `# ignore everything because the directory is auto-generated by inlang paraglide-js
# for more info visit https://inlang.com/m/gerre34r/paraglide-js
*
`

export type CompileOptions = {
	messages: Readonly<Message[]>
	settings: ProjectSettings
	projectId?: string | undefined
	/**
	 * The file-structure of the compiled output.
	 *
	 * @default "regular"
	 */
	outputStructure?: "regular" | "message-modules"
}

const defaultCompileOptions = {
	projectId: undefined,
	outputStructure: "regular",
} satisfies Partial<CompileOptions>

/**
 * A compile function takes a list of messages and project settings and returns
 * a map of file names to file contents.
 *
 * @example
 *   const output = compile({ messages, settings })
 *   console.log(output)
 *   >> { "messages.js": "...", "runtime.js": "..." }
 */
export const compile = async (args: CompileOptions): Promise<Record<string, string>> => {
	const opts = {
		...defaultCompileOptions,
		...args,
	}

	//Maps each language to it's fallback
	//If there is no fallback, it will be undefined
	const fallbackMap: Record<LanguageTag, LanguageTag | undefined> = getFallbackMap(
		opts.settings.languageTags,
		opts.settings.sourceLanguageTag
	)

	const compiledMessages = opts.messages.map((message) =>
		compileMessage(message, fallbackMap, opts.outputStructure)
	)

	const pkgJson = await getPackageJson(fs, process.cwd())
	const stack = getStackInfo(pkgJson)

	telemetry.capture(
		{
			event: "PARAGLIDE-JS compile executed",
			properties: {
				stack,
			},
		},
		opts.projectId
	)

	const resources: Record<string, string> = {}

	for (const compiledMessage of compiledMessages) {
		for (const languageTag of Object.keys(compiledMessage.translations)) {
			if (languageTag === "index") continue
			if (!resources[languageTag]) resources[languageTag] = ""
			resources[languageTag] += "\n\n" + compiledMessage.translations[languageTag]
		}
	}

	const languagesWithMessages = new Set<LanguageTag>(Object.keys(resources))

	const languagesWithoutMessages = opts.settings.languageTags.filter(
		(languageTag) => !languagesWithMessages.has(languageTag)
	)

	// only add fallback content if there isn't yet a file with that language
	for (const languageTag of languagesWithoutMessages) {
		if (!resources[languageTag]) resources[languageTag] = "\n\nexport {};"
	}

	telemetry.shutdown()

	let output: Record<string, string> = {
		// boilerplate files
		".prettierignore": ignoreDirectory,
		".gitignore": ignoreDirectory,
		"runtime.js": createRuntime(opts.settings),
	}

	if (opts.outputStructure === "message-modules") {
		// add all the index files
		for (const message of compiledMessages) {
			output[`messages/index/${message.source.id}.js`] = [
				"/* eslint-disable */",
				'import { languageTag } from "../../runtime.js"',
				opts.settings.languageTags
					.map(
						(languageTag) =>
							`import * as ${i(languageTag)} from "../${languageTag}/${message.source.id}.js"`
					)
					.join("\n"),
				"\n",
				message.index,
			].join("\n")

			// add all the message files
			for (const [lang, source] of Object.entries(message.translations)) {
				output[`messages/${lang}/${message.source.id}.js`] = [
					"/* eslint-disable */",
					`/** 
* This file contains language specific message functions for tree-shaking. 
* 
*! WARNING: Only import messages from this file if you want to manually
*! optimize your bundle. Else, import from the \`messages.js\` file. 
* 
* Your bundler will (in the future) automatically replace the index function 
* with a language specific message function in the build step. 
*/`,
					source,
				].join("\n")
			}
		}

		const messageIDs = compiledMessages.map((message) => message.source.id)

		// add the barrel files
		output["messages.js"] = [
			"/* eslint-disable */",
			...messageIDs.map((id) => `export * from "./messages/index/${id}.js"`),
		].join("\n")

		for (const languageTag of opts.settings.languageTags) {
			output[`messages/${languageTag}.js`] = [
				"/* eslint-disable */",
				...(messageIDs.length === 0
					? ["export {}"]
					: messageIDs.map((id) => `export * from "./${languageTag}/${id}.js"`)),
			].join("\n")
		}
	} else {
		output = {
			...output,
			// message index file
			"messages.js": [
				"/* eslint-disable */",
				'import { languageTag } from "./runtime.js"',
				opts.settings.languageTags
					.map((languageTag) => `import * as ${i(languageTag)} from "./messages/${languageTag}.js"`)
					.join("\n"),
				"\n",
				compiledMessages.map((message) => message.index).join("\n\n"),
			].join("\n"),
			...Object.fromEntries(
				Object.entries(resources).map(([languageTag, content]) => [
					`messages/${languageTag}.js`,
					[
						"/* eslint-disable */",
						`/** 
 * This file contains language specific message functions for tree-shaking. 
 * 
 *! WARNING: Only import messages from this file if you want to manually
 *! optimize your bundle. Else, import from the \`messages.js\` file. 
 * 
 * Your bundler will (in the future) automatically replace the index function 
 * with a language specific message function in the build step. 
 */`,
						content,
					].join("\n"),
				])
			),
		}
	}

	return output
}

export function getFallbackMap(languageTags: LanguageTag[], sourceLanguageTag: LanguageTag) {
	return Object.fromEntries(
		languageTags.map((lang) => {
			const fallbackLanguage = lookup(lang, {
				languageTags: languageTags.filter((t) => t !== lang),
				defaultLanguageTag: sourceLanguageTag,
			})

			if (lang === fallbackLanguage) return [lang, undefined]
			else return [lang, fallbackLanguage]
		})
	)
}
